package sep.test;

import static com.googlecode.javacv.cpp.avcodec.CODEC_ID_MPEG4;
import static com.googlecode.javacv.cpp.avutil.PIX_FMT_YUV420P;

import java.awt.image.BufferedImage;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Observable;

import com.googlecode.javacv.FFmpegFrameGrabber;
import com.googlecode.javacv.FFmpegFrameRecorder;
import com.googlecode.javacv.FrameGrabber;
import com.googlecode.javacv.FrameGrabber.Exception;
import com.googlecode.javacv.FrameRecorder;
import com.googlecode.javacv.OpenCVFrameGrabber;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

// TODO Manchmal keine Scanlines, bzw. an falscher Position. Bug?

public class IdaModel extends Observable implements IdaWorkerListener {

	public enum PlaybackState {
		Playing, Stopped, Paused
	}

	public enum PlaybackSource {
		Camera, File, None
	}

	private final static String RECORD_FILE_EXTENSION = "mp4";
	private final static String RECORD_FILE_DATE_FORMAT = "y-MM-d-H-m-s";

	private PlaybackSource source = PlaybackSource.None;
	private IdaWorkerThread thread = null;
	private IdaImageWriterThread writerThread = null;
	private BufferedImage image = null;

	private volatile boolean startRecording = false;

	public boolean openCamera() {
		if (openVideo(new OpenCVFrameGrabber(0))) {
			source = PlaybackSource.Camera;
			return true;
		} else {
			source = PlaybackSource.None;
			return false;
		}
	}

	public boolean openVideoFile(File file) {
		if (file == null)
			throw new IllegalArgumentException("File must not be null.");

		if (openVideo(new FFmpegFrameGrabber(file))) {
			source = PlaybackSource.File;
			return true;
		} else {
			source = PlaybackSource.None;
			return false;
		}
	}

	private boolean openVideo(FrameGrabber grabber) {
		// Erst WorkerThread beenden
		quit();

		try {
			// Grabber starten, um zu sehen, ob die Videoquelle ge�ffnet
			// werden
			// kann
			grabber.start();

			// WorkerThread erstellen und starten
			thread = new IdaWorkerThread(grabber, this);
			thread.start();
		} catch (Exception e) {
			System.out.println(e.getMessage());
			return false;
		}

		return true;
	}

	public void quit() {
		// WorkerThread beenden
		if (thread != null) {
			thread.quit();

			// Warten, bis Thread beendet wurde
			while (thread.isAlive())
				;

			thread = null;
			source = PlaybackSource.None;
		}

		stopRecord();
	}

	public boolean play() {
		if (source.equals(PlaybackSource.None) || thread == null) {
			return false;
		} else {
			return thread.play();
		}
	}

	public boolean pause() {
		if (source.equals(PlaybackSource.None) || thread == null) {
			return false;
		} else {
			thread.pausePlayback();
			return true;
		}
	}

	public boolean stop() {
		if (source.equals(PlaybackSource.None) || thread == null) {
			return false;
		} else {
			thread.stopPlayback();
			return true;
		}
	}

	public boolean singleStep() {
		if (source.equals(PlaybackSource.None) || thread == null) {
			return false;
		} else {
			return thread.singleStep();
		}
	}

	public PlaybackState getPlaybackState() {
		if (thread == null)
			return PlaybackState.Stopped;
		else
			return thread.getPlaybackState();
	}

	public PlaybackSource getPlaybackSource() {
		return source;
	}

	public BufferedImage getImage() {
		return image;
	}

	public void resetStateFilter() {
		IdaSettings.getInstance().getStateFilter().reset();
	}

	public void startRecord() {
		// Evtl. laufende Aufnahme zuerst stoppen
		stopRecord();

		// Thread und FrameWriter werden erst erstellt, wenn ein neues Bild vom
		// Worker Thread eintrifft, da sonst Bildgr�sse und Framerate
		// unbekannt
		// sind.
		startRecording = true;
	}

	private boolean createWriterThread(final int width, final int height,
			double frameRate) {
		final SimpleDateFormat format = new SimpleDateFormat(
				RECORD_FILE_DATE_FORMAT, Locale.US);
		final String filename = IdaSettings.getInstance().getVideoSavePath()
				+ "/" + format.format(new Date()) + "." + RECORD_FILE_EXTENSION;

		// Kamera liefert keine Framerate (=0.0)
		// => improvisieren ?
		if (frameRate < 0.1) {
			frameRate = 25.0;
		}

		FrameRecorder recorder = new FFmpegFrameRecorder(filename, width,
				height, 0);

		try {
			recorder.setVideoCodec(CODEC_ID_MPEG4);
			recorder.setPixelFormat(PIX_FMT_YUV420P);
			recorder.setFormat(RECORD_FILE_EXTENSION);
			recorder.setFrameRate(frameRate);

			// Recorder starten
			recorder.start();

			// Thread starten
			writerThread = new IdaImageWriterThread(recorder);
			writerThread.start();
			return true;
		} catch (FrameRecorder.Exception e) {
			System.out.println(e.getMessage());
			return false;
		}
	}

	public void stopRecord() {
		startRecording = false;

		if (writerThread != null) {
			synchronized (writerThread) {
				writerThread.quit();
				writerThread = null;
			}
		}
	}

	@Override
	public void update() {
		if (thread != null) {
			final IplImage iplImage = thread.getImage();

			if (iplImage != null) {
				image = iplImage.getBufferedImage();

				if (startRecording) {
					startRecording = false;
					createWriterThread(iplImage.width(), iplImage.height(),
							thread.getFrameRate());
				}

				// Bild speichern, falls aktiv
				if (writerThread != null) {
					synchronized (writerThread) {
						writerThread.addImage(iplImage);
					}
				}
			} else {
				image = null;
			}
		}

		setChanged();
		notifyObservers();
	}

}
